
namespace ItProject.Andir.Cryptography
{
	internal abstract class BaseHavalImpl
		: IBlockHashAlghorithmImpl
	{
		protected static readonly uint[] _wordOrders = new uint[]
		{
			// pass 2
			05, 14, 26, 18, 11, 28, 07, 16, 00, 23, 20, 22, 01, 10, 04, 08,
			30, 03, 21, 09, 17, 24, 29, 06, 19, 12, 15, 13, 02, 25, 31, 27,
			// pass 3
			19, 09, 04, 20, 28, 17, 08, 22, 29, 14, 25, 12, 24, 30, 16, 26,
			31, 15, 07, 03, 01, 00, 18, 27, 13, 06, 21, 10, 23, 11, 05, 02,
			// pass 4
			24, 04, 00, 14, 02, 07, 28, 23, 26, 06, 30, 20, 18, 25, 19, 03,
			22, 11, 31, 21, 08, 27, 12, 09, 01, 29, 05, 15, 17, 10, 16, 13,
			// pass 5
			27, 03, 21, 26, 17, 11, 20, 29, 19, 00, 12, 07, 13, 08, 31, 10,
			05, 09, 14, 30, 18, 06, 28, 24, 02, 23, 16, 22, 04, 01, 25, 15
		};

		protected static readonly uint[] _constants = new uint[]
		{
			// pass 2
			0x452821e6,	0x38d01377,	0xbe5466cf,	0x34e90c6c,	0xc0ac29b7,	0xc97c50dd,	0x3f84d5b5,	0xb5470917,
			0x9216d5d9,	0x8979fb1b,	0xd1310ba6,	0x98dfb5ac,	0x2ffd72db,	0xd01adfb7,	0xb8e1afed,	0x6a267e96,
			0xba7c9045,	0xf12c7f99,	0x24a19947,	0xb3916cf7,	0x0801f2e2,	0x858efc16,	0x636920d8,	0x71574e69,
			0xa458fea3,	0xf4933d7e,	0x0d95748f,	0x728eb658,	0x718bcd58,	0x82154aee,	0x7b54a41d,	0xc25a59b5,
			// pass 3
			0x9c30d539,	0x2af26013,	0xc5d1b023,	0x286085f0,	0xca417918,	0xb8db38ef,	0x8e79dcb0,	0x603a180e,
			0x6c9e0e8b,	0xb01e8a3e, 0xd71577c1,	0xbd314b27,	0x78af2fda,	0x55605c60,	0xe65525f3,	0xaa55ab94,
			0x57489862,	0x63e81440,	0x55ca396a,	0x2aab10b6,	0xb4cc5c34,	0x1141e8ce,	0xa15486af,	0x7c72e993,
			0xb3ee1411,	0x636fbc2a,	0x2ba9c55d,	0x741831f6,	0xce5c3e16,	0x9b87931e,	0xafd6ba33,	0x6c24cf5c,
			// pass 4			
			0x7a325381,	0x28958677,	0x3b8f4898,	0x6b4bb9af,	0xc4bfe81b,	0x66282193,	0x61d809cc,	0xfb21a991,
			0x487cac60,	0x5dec8032,	0xef845d5d,	0xe98575b1,	0xdc262302,	0xeb651b88,	0x23893e81,	0xd396acc5,
			0x0f6d6ff3,	0x83f44239,	0x2e0b4482,	0xa4842004,	0x69c8f04a,	0x9e1f9b5e,	0x21c66842,	0xf6e96c9a,
			0x670c9c61,	0xabd388f0,	0x6a51a0d2,	0xd8542f68,	0x960fa728,	0xab5133a3,	0x6eef0b6c,	0x137a3be4,
			// pass 5	
			0xba3bf050,	0x7efb2a98,	0xa1f1651d,	0x39af0176,	0x66ca593e,	0x82430e88,	0x8cee8619,	0x456f9fb4,
			0x7d84a5c3,	0x3b8b5ebe,	0xe06f75d8,	0x85c12073,	0x401a449f,	0x56c16aa6,	0x4ed3aa62,	0x363f7706,
			0x1bfedf72,	0x429b023d,	0x37d0d724,	0xd00a1248,	0xdb0fead3,	0x49f1c09b,	0x075372c9,	0x80991b7b,
			0x25d479d8,	0xf6e8def7,	0xe3fe501a,	0xb6794c3b,	0x976ce0bd,	0x04c006ba,	0xc1a94fb6,	0x409f60c4
		};
				
		protected static byte[] _signature = new byte[2];
		private HavalHashSize _havalHashSize;

		public BaseHavalImpl()
			: this(HavalHashSize.HashSize256)
		{ }

		public BaseHavalImpl(HavalHashSize size)
		{
			_havalHashSize = size;
			_finalBlock = new byte[BlockSize]; 
		}

		public int BlockSize
		{
			get { return 128; }
		}

		private byte[] _finalBlock;
		protected uint[] _state = new uint[8];
		
		private int[] _processedLength = new int[2];

		protected void AddProcessedLength(int length)
		{
			if (int.MaxValue - length < _processedLength[1])
			{
				_processedLength[0]++;

				_processedLength[1] = int.MaxValue - _processedLength[1];
				_processedLength[1] = length - _processedLength[1];
			}
			else
			{
				_processedLength[1] += length;
			}
		}

		private void ClearProcessedLength()
		{
			_processedLength[0] = 0;
			_processedLength[1] = 0;
		}
		
		public void Initialize()
		{
			ClearProcessedLength();

			System.Array.Clear(_finalBlock, 0, _finalBlock.Length);
			
			InitializeState();
		}

		private void InitializeState()
		{
			_state[0] = 0x243f6a88;
			_state[1] = 0x85a308d3;
			_state[2] = 0x13198a2e;
			_state[3] = 0x03707344;
			_state[4] = 0xa4093822;
			_state[5] = 0x299f31d0; 
			_state[6] = 0x082efa98;
			_state[7] = 0xec4e6c89;
		}

		protected uint[] _buff = new uint[160];

		public abstract void ProcessBlock(byte[] array, int offset);

		public void ProcessFinalBlock(byte[] array, int offset, int length)
		{			
			if(length >= BlockSize)
				throw new System.IndexOutOfRangeException("Name: length");

			if(array.Length - offset < length)
				throw new System.IndexOutOfRangeException("Name: length");

			AddProcessedLength(length << 3); // arg * 8

			int[] messageLength = new int[2];

			messageLength[0] = _processedLength[0];
			messageLength[1] = _processedLength[1];

			System.Buffer.BlockCopy(array, offset, _finalBlock, 0, length);

			// padding message with 100..000 bits
            _finalBlock[length] = 0x80;// 0x01;

			int endOffset = BlockSize - 10;

			if(length >= endOffset)
			{
				ProcessBlock(_finalBlock, 0);

				System.Array.Clear(_finalBlock, 0, _finalBlock.Length);
			}

			_finalBlock[endOffset + 0] = _signature[0];
			_finalBlock[endOffset + 1] = _signature[1];
			
			endOffset += 2;

			int highWord = messageLength[0];
			int lowWord = messageLength[1];

			_finalBlock[endOffset + 0] = (byte)(lowWord);
			_finalBlock[endOffset + 1] = (byte)(lowWord >> 8);
			_finalBlock[endOffset + 2] = (byte)(lowWord >> 16);
			_finalBlock[endOffset + 3] = (byte)(lowWord >> 24);

			endOffset += 4;

			_finalBlock[endOffset + 0] = (byte)(highWord);
			_finalBlock[endOffset + 1] = (byte)(highWord >> 8);
			_finalBlock[endOffset + 2] = (byte)(highWord >> 16);
			_finalBlock[endOffset + 3] = (byte)(highWord >> 24);

			// Processing of last block
			ProcessBlock(_finalBlock, 0);
		}

		public byte[] Result
		{
			get 
			{ 
				TailorResult();

				byte[] result = new byte[(int)_havalHashSize >> 3];					

				System.Buffer.BlockCopy(_state, 0, result, 0, result.Length);

				return result;
			}
		}

		private void TailorResult()
		{
			TailorResult(_havalHashSize);
		}

		private void TailorResult(HavalHashSize size)
		{
			switch(size)
			{
				case HavalHashSize.HashSize128: TailorResult128(); break;
				case HavalHashSize.HashSize160: TailorResult160(); break;
				case HavalHashSize.HashSize192: TailorResult192(); break;
				case HavalHashSize.HashSize224: TailorResult224(); break;
				case HavalHashSize.HashSize256: break;
			}
		}

		private void TailorResult128()
		{
			uint temp;

			temp =  (_state[7] & 0x000000ff) | 
					(_state[6] & 0xff000000) | 
					(_state[5] & 0x00ff0000) | 
					(_state[4] & 0x0000ff00);

			_state[0] += temp >> 8 | temp << 24;

			temp =  (_state[7] & 0x0000ff00) | 
					(_state[6] & 0x000000ff) | 
					(_state[5] & 0xff000000) | 
					(_state[4] & 0x00ff0000);

			_state[1] += temp >> 16 | temp << 16;

			temp =  (_state[7] & 0x00ff0000) | 
					(_state[6] & 0x0000ff00) | 
					(_state[5] & 0x000000ff) | 
					(_state[4] & 0xff000000);

			_state[2] += temp >> 24 | temp << 8;

			temp =  (_state[7] & 0xff000000) | 
					(_state[6] & 0x00ff0000) | 
					(_state[5] & 0x0000ff00) | 
					(_state[4] & 0x000000ff);

			_state[3] += temp;
		}

		private void TailorResult160()
		{
			uint temp;

			temp =  (_state[7] & (0x0000003fU << 00)) | 
					(_state[6] & (0x0000007fU << 25)) |  
					(_state[5] & (0x0000003fU << 19));
			_state[0] += temp >> 19 | temp << 13;

			temp =  (_state[7] & (0x0000003fU << 06)) | 
					(_state[6] & (0x0000003fU << 00)) |  
					(_state[5] & (0x0000007fU << 25));
			_state[1] += temp >> 25 | temp << 7;

			temp =  (_state[7] & (0x0000007fU << 12)) | 
					(_state[6] & (0x0000003fU << 06)) |  
					(_state[5] & (0x0000003fU << 00));
			_state[2] += temp;

			temp =  (_state[7] & (0x0000003fU << 19)) | 
					(_state[6] & (0x0000007fU << 12)) |  
					(_state[5] & (0x0000003fU << 06));
			_state[3] += temp >> 6;

			temp =  (_state[7] & (0x0000007fU << 25)) | 
					(_state[6] & (0x0000003fU << 19)) |  
					(_state[5] & (0x0000007fU << 12));
			_state[4] += temp >> 12;
		}

		private void TailorResult192()
		{
			uint temp;

			temp =  (_state[7] & (0x0000001fU << 00)) | 
					(_state[6] & (0x0000003fU << 26));
			_state[0] += temp >> 26 | temp << 6;

			temp =  (_state[7] & (0x0000001fU << 05)) | 
					(_state[6] & (0x0000001fU << 00));
			_state[1] += temp;

			temp =  (_state[7] & (0x0000003fU << 10)) | 
					(_state[6] & (0x0000001fU << 05));
			_state[2] += temp >> 5;

			temp =  (_state[7] & (0x0000001fU << 16)) | 
					(_state[6] & (0x0000003fU << 10));
			_state[3] += temp >> 10;

			temp =  (_state[7] & (0x0000001fU << 21)) | 
					(_state[6] & (0x0000001fU << 16));
			_state[4] += temp >> 16;
	
			temp =  (_state[7] & (0x0000003fU << 26)) | 
					(_state[6] & (0x0000001fU << 21));
			_state[5] += temp >> 21;
		}

		private void TailorResult224()
		{
			_state[0] += (_state[7] >> 27) & 0x1f;
			_state[1] += (_state[7] >> 22) & 0x1f;
			_state[2] += (_state[7] >> 18) & 0x0f;
			_state[3] += (_state[7] >> 13) & 0x1f;
			_state[4] += (_state[7] >> 09) & 0x0f;
			_state[5] += (_state[7] >> 04) & 0x1f;
			_state[6] += (_state[7] >> 00) & 0x0f;
		}

		protected uint f1(uint x6, uint x5, uint x4, uint x3, uint x2, uint x1, uint x0)
		{
			return x1 & (x0 ^ x4) ^ x2 & x5 ^ x3 & x6 ^ x0;
		}

		protected uint f2(uint x6, uint x5, uint x4, uint x3, uint x2, uint x1, uint x0)
		{
			return x2 & (x1 & ~x3 ^ x4 & x5 ^ x6 ^ x0) ^ x4 & (x1 ^ x5) ^ x3 & x5 ^ x0;
		}

		protected uint f3(uint x6, uint x5, uint x4, uint x3, uint x2, uint x1, uint x0)
		{
			return x3 & (x1 & x2 ^ x6 ^ x0) ^ x1 & x4 ^ x2 & x5 ^ x0;
		}

		protected uint f4(uint x6, uint x5, uint x4, uint x3, uint x2, uint x1, uint x0)
		{
			return x4 & (x5 & ~x2 ^ x3 & ~x6 ^ x1 ^ x6 ^ x0) ^ x3 & (x1 & x2 ^ x5 ^ x6) ^ x2 & x6 ^ x0;
		}

		protected uint f5(uint x6, uint x5, uint x4, uint x3, uint x2, uint x1, uint x0)
		{
			return x0 & (x1 & x2 & x3 ^ ~x5) ^ x1 & x4 ^ x2 & x5 ^ x3 & x6;
		}		 
	}
}
